package com.mdb.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.slf4j.LoggerFactory;

final class ClassFinder
{
  private final List<Class<?>> classes = new ArrayList<Class<?>>();
  private final String packageName;

  public ClassFinder(String packageName)
    throws Exception
  {
    this.packageName = packageName;

    ClassLoader loader = ClassFinder.class.getClassLoader();
    Enumeration<?> resources = loader.getResources(name2Path(packageName));

    while (resources.hasMoreElements()) {
      URL resource = (URL)resources.nextElement();
      if (resource.getProtocol().equalsIgnoreCase("jar"))
      {
        String path = resource.getPath();
        findInJar(new File(path.substring("file:".length(), path.indexOf('!')))); } else {
        if (!resource.getProtocol().equalsIgnoreCase("file"))
          continue;
        findInDirectory(new File(resource.toURI()), ".");
      }
    }
  }

  public List<Class<?>> getClasses()
  {
    return this.classes;
  }

  private void addClass(String className)
  {
    try
    {
      this.classes.add(Class.forName(className));
    }
    catch (Throwable t) {
      LoggerFactory.getLogger(getClass()).warn("Error to load class:{}", className);
    }
  }

  private void findInDirectory(File directory, String childName)
    throws IOException
  {
    File[] files = directory.listFiles();

    if (files == null) {
      return;
    }

    for (File file : files)
    {
      String name = file.getName();
      if (file.isDirectory())
      {
        findInDirectory(file, childName + name + "."); } else {
        if ((!file.isFile()) || (!name.toLowerCase().endsWith(".class")))
          continue;
        addClass(this.packageName + childName + removeSuffix(name, ".class"));
      }
    }
  }

  private void findInJar(File file)
    throws IOException
  {
    JarFile jar = null;
    try
    {
      jar = new JarFile(file);
      Enumeration<?> enumeration = jar.entries();

      while (enumeration.hasMoreElements())
      {
        JarEntry entry = (JarEntry)enumeration.nextElement();

        String name = entry.getName();

        if (!name.toLowerCase().endsWith(".class"))
        {
          continue;
        }

        String className = path2Name(removeSuffix(name, ".class"));
        if (!className.startsWith(this.packageName))
          continue;
        addClass(className);
      }
    }
    finally {
      if (jar != null)
        jar.close();
    }
  }

  private static String removeSuffix(String str, String suffix)
  {
    return str.substring(0, str.length() - suffix.length());
  }

  private static String path2Name(String path)
  {
    return path.replace('/', '.').replace('\\', '.');
  }

  private static String name2Path(String name)
  {
    return name.replace('.', '/');
  }

  public static void main(String[] args) throws Exception
  {
    System.out.println(new ClassFinder("com.game").getClasses().size());
  }
}
